gsk: Add GskBorderNode
authorBenjamin Otte <otte@redhat.com>
Sun, 18 Dec 2016 17:14:53 +0000 (18:14 +0100)
committerBenjamin Otte <otte@redhat.com>
Tue, 20 Dec 2016 17:01:11 +0000 (18:01 +0100)
The node draws a solid CSS border, which can be used to cover everything
but dashed and dotted borders (double, groove, inset, ...).

For different border styles, we overlay multiple nodes and set their
colors to transparent for sides with non-matching styles.

docs/reference/gsk/gsk4-sections.txt
gsk/gskenums.h
gsk/gskrendernode.h
gsk/gskrendernodeimpl.c
gsk/gskrendernodeprivate.h
gtk/gtkrenderborder.c
gtk/gtkrenderborderprivate.h
gtk/inspector/gtktreemodelrendernode.c

index 3b47dab5179977a207816547b6962e6d4238483c..023abe5d562588c47a86d7e1347a017c5e19afc3 100644 (file)
@@ -35,6 +35,7 @@ gsk_render_node_set_name
 gsk_color_node_new
 gsk_linear_gradient_node_new
 gsk_repeating_linear_gradient_node_new
+gsk_border_node_new
 gsk_texture_node_new
 gsk_cairo_node_new
 gsk_cairo_node_get_draw_context
index 1d46c9230eb1b334f08329aec820723e25b489bd..7bad39d1c29774314633dd170828445648078d8e 100644 (file)
@@ -31,6 +31,7 @@
  * @GSK_LINEAR_GRADIENT_NODE: A node drawing a linear gradient
  * @GSK_REPEATING_LINEAR_GRADIENT_NODE: A node drawing a repeating
  *     linear gradient
+ * @GSK_BORDER_NODE: A node stroking a border around an area
  * @GSK_TEXTURE_NODE: A node drawing a #GskTexture
  * @GSK_TRANSFORM_NODE: A node that renders its child after applying a
  *     matrix transform
@@ -51,6 +52,7 @@ typedef enum {
   GSK_COLOR_NODE,
   GSK_LINEAR_GRADIENT_NODE,
   GSK_REPEATING_LINEAR_GRADIENT_NODE,
+  GSK_BORDER_NODE,
   GSK_TEXTURE_NODE,
   GSK_TRANSFORM_NODE,
   GSK_OPACITY_NODE,
index 14fa797db996f8af1273ed6d4dd1dbd97ce9f9f0..a349b8b302a17c0003d910d260c3ea7d5d32ffd8 100644 (file)
@@ -73,6 +73,11 @@ GskRenderNode *         gsk_repeating_linear_gradient_node_new  (const graphene_
                                                                  const GskColorStop       *color_stops,
                                                                  gsize                     n_color_stops);
 
+GDK_AVAILABLE_IN_3_90
+GskRenderNode *         gsk_border_node_new                     (const GskRoundedRect     *outline,
+                                                                 const float               border_width[4],
+                                                                 const GdkRGBA             border_color[4]);
+
 GDK_AVAILABLE_IN_3_90
 GskRenderNode *         gsk_cairo_node_new                      (const graphene_rect_t    *bounds);
 GDK_AVAILABLE_IN_3_90
index ee3cb460158f4d9863ba59b8672c33a4bc09c67a..c5d8d615d3a06191f8c00e552bf782b3316b76ab 100644 (file)
@@ -271,6 +271,192 @@ gsk_repeating_linear_gradient_node_new (const graphene_rect_t  *bounds,
   return &self->render_node;
 }
 
+/*** GSK_BORDER_NODE ***/
+
+typedef struct _GskBorderNode GskBorderNode;
+
+struct _GskBorderNode
+{
+  GskRenderNode render_node;
+
+  GskRoundedRect outline;
+  float border_width[4];
+  GdkRGBA border_color[4];
+};
+
+static void
+gsk_border_node_finalize (GskRenderNode *node)
+{
+}
+
+static void
+gsk_border_node_make_immutable (GskRenderNode *node)
+{
+}
+
+static void
+gsk_border_node_draw (GskRenderNode *node,
+                       cairo_t       *cr)
+{
+  GskBorderNode *self = (GskBorderNode *) node;
+  GskRoundedRect inside;
+
+  cairo_save (cr);
+
+  gsk_rounded_rect_init_copy (&inside, &self->outline);
+  gsk_rounded_rect_shrink (&inside,
+                           self->border_width[0], self->border_width[1],
+                           self->border_width[2], self->border_width[3]);
+
+  cairo_set_fill_rule (cr, CAIRO_FILL_RULE_EVEN_ODD);
+  gsk_rounded_rect_path (&self->outline, cr);
+  gsk_rounded_rect_path (&inside, cr);
+
+  if (gdk_rgba_equal (&self->border_color[0], &self->border_color[1]) &&
+      gdk_rgba_equal (&self->border_color[0], &self->border_color[2]) &&
+      gdk_rgba_equal (&self->border_color[0], &self->border_color[3]))
+    {
+      gdk_cairo_set_source_rgba (cr, &self->border_color[0]);
+      cairo_fill (cr);
+    }
+  else
+    {
+      const graphene_rect_t *bounds = &self->outline.bounds;
+      /* distance to center "line":
+       * +-------------------------+
+       * |                         |
+       * |                         |
+       * |     ---this-line---     |
+       * |                         |
+       * |                         |
+       * +-------------------------+
+       * That line is equidistant from all sides. It's either horiontal
+       * or vertical, depending on if the rect is wider or taller.
+       * We use the 4 sides spanned up by connecting the line to the corner
+       * points to color the regions of the rectangle differently.
+       * Note that the call to cairo_fill() will add the potential final
+       * segment by closing the path, so we don't have to care.
+       */
+      float dst = MIN (bounds->size.width, bounds->size.height) / 2.0;
+
+      cairo_clip (cr);
+
+      /* top */
+      cairo_move_to (cr, bounds->origin.x + dst, bounds->origin.y + dst);
+      cairo_rel_line_to (cr, - dst, - dst);
+      cairo_rel_line_to (cr, bounds->size.width, 0);
+      cairo_rel_line_to (cr, - dst, dst);
+      gdk_cairo_set_source_rgba (cr, &self->border_color[0]);
+      cairo_fill (cr);
+
+      /* right */
+      cairo_move_to (cr, bounds->origin.x + bounds->size.width - dst, bounds->origin.y + dst);
+      cairo_rel_line_to (cr, dst, - dst);
+      cairo_rel_line_to (cr, 0, bounds->size.height);
+      cairo_rel_line_to (cr, - dst, - dst);
+      gdk_cairo_set_source_rgba (cr, &self->border_color[1]);
+      cairo_fill (cr);
+
+      /* bottom */
+      cairo_move_to (cr, bounds->origin.x + bounds->size.width - dst, bounds->origin.y + bounds->size.height - dst);
+      cairo_rel_line_to (cr, dst, dst);
+      cairo_rel_line_to (cr, - bounds->size.width, 0);
+      cairo_rel_line_to (cr, dst, - dst);
+      gdk_cairo_set_source_rgba (cr, &self->border_color[2]);
+      cairo_fill (cr);
+
+      /* left */
+      cairo_move_to (cr, bounds->origin.x + dst, bounds->origin.y + bounds->size.height - dst);
+      cairo_rel_line_to (cr, - dst, dst);
+      cairo_rel_line_to (cr, 0, - bounds->size.height);
+      cairo_rel_line_to (cr, dst, dst);
+      gdk_cairo_set_source_rgba (cr, &self->border_color[3]);
+      cairo_fill (cr);
+    }
+
+  cairo_restore (cr);
+}
+
+static void
+gsk_border_node_get_bounds (GskRenderNode   *node,
+                            graphene_rect_t *bounds)
+{
+  GskBorderNode *self = (GskBorderNode *) node;
+
+  graphene_rect_init_from_rect (bounds, &self->outline.bounds);
+}
+
+static const GskRenderNodeClass GSK_BORDER_NODE_CLASS = {
+  GSK_BORDER_NODE,
+  sizeof (GskBorderNode),
+  "GskBorderNode",
+  gsk_border_node_finalize,
+  gsk_border_node_make_immutable,
+  gsk_border_node_draw,
+  gsk_border_node_get_bounds
+};
+
+const GskRoundedRect *
+gsk_border_node_peek_outline (GskRenderNode *node)
+{
+  GskBorderNode *self = (GskBorderNode *) node;
+
+  return &self->outline;
+}
+
+float
+gsk_border_node_get_width (GskRenderNode *node,
+                           guint          i)
+{
+  GskBorderNode *self = (GskBorderNode *) node;
+
+  return self->border_width[i];
+}
+
+const GdkRGBA *
+gsk_border_node_peek_color (GskRenderNode *node,
+                            guint          i)
+{
+  GskBorderNode *self = (GskBorderNode *) node;
+
+  return &self->border_color[i];
+}
+
+/**
+ * gsk_border_node_new:
+ * @outline: a #GskRoundedRect describing the outline of the border
+ * @border_width: the stroke width of the border on the top, right, bottom and
+ *     left side respectively.
+ * @border_color: the color used on the top, right, bottom and left side.
+ *
+ * Creates a #GskRenderNode that will stroke a border rectangle inside the
+ * given @outline. The 4 sides of the border can have different widths and
+ * colors.
+ *
+ * Returns: A new #GskRenderNode
+ *
+ * Since: 3.90
+ */
+GskRenderNode *
+gsk_border_node_new (const GskRoundedRect     *outline,
+                     const float               border_width[4],
+                     const GdkRGBA             border_color[4])
+{
+  GskBorderNode *self;
+
+  g_return_val_if_fail (outline != NULL, NULL);
+  g_return_val_if_fail (border_width != NULL, NULL);
+  g_return_val_if_fail (border_color != NULL, NULL);
+
+  self = (GskBorderNode *) gsk_render_node_new (&GSK_BORDER_NODE_CLASS);
+
+  gsk_rounded_rect_init_copy (&self->outline, outline);
+  memcpy (self->border_width, border_width, sizeof (self->border_width));
+  memcpy (self->border_color, border_color, sizeof (self->border_color));
+
+  return &self->render_node;
+}
+
 /*** GSK_TEXTURE_NODE ***/
 
 typedef struct _GskTextureNode GskTextureNode;
index e945ee7b2cdd80dd074d38b5f73cb0402b455a2a..24e87e8880f3e48de5f58f328b6a3124f74cf9b8 100644 (file)
@@ -48,6 +48,10 @@ void gsk_render_node_get_bounds (GskRenderNode   *node,
                                  graphene_rect_t *frame);
 double gsk_opacity_node_get_opacity (GskRenderNode *node);
 
+const GskRoundedRect * gsk_border_node_peek_outline (GskRenderNode *node);
+float gsk_border_node_get_width (GskRenderNode *node, guint i);
+const GdkRGBA * gsk_border_node_peek_color (GskRenderNode *node, guint i);
+
 cairo_surface_t *gsk_cairo_node_get_surface (GskRenderNode *node);
 
 GskTexture *gsk_texture_node_get_texture (GskRenderNode *node);
index 5a17516499b2be817659383d9d7615d5daf1ccd1..7f739c3d643061c40358966ec223d6bf577fdb0a 100644 (file)
@@ -405,6 +405,44 @@ render_frame_fill (cairo_t        *cr,
     }
 }
 
+static void
+snapshot_frame_fill (GtkSnapshot          *snapshot,
+                     const GskRoundedRect *outline,
+                     const float           border_width[4],
+                     const GdkRGBA         colors[4],
+                     guint                 hidden_side)
+{
+  GskRoundedRect offset_outline;
+  GskRenderNode *node;
+  double off_x, off_y;
+
+  if (hidden_side)
+    {
+      GdkRGBA real_colors[4];
+      guint i;
+
+      for (i = 0; i < 4; i++)
+        {
+          if (hidden_side & (1 << i))
+            real_colors[i] = (GdkRGBA) { 0, 0, 0, 0 };
+          else
+            real_colors[i] = colors[i];
+        }
+
+      snapshot_frame_fill (snapshot, outline, border_width, real_colors, 0);
+      return;
+    }
+
+  gtk_snapshot_get_offset (snapshot, &off_x, &off_y);
+  gsk_rounded_rect_init_copy (&offset_outline, outline);
+  gsk_rounded_rect_offset (&offset_outline, off_x, off_y);
+  
+  node = gsk_border_node_new (&offset_outline, border_width, colors);
+  gsk_render_node_set_name (node, "Border");
+  gtk_snapshot_append_node (snapshot, node);
+  gsk_render_node_unref (node);
+}
+
 static void
 set_stroke_style (cairo_t        *cr,
                   double          line_width,
@@ -539,6 +577,24 @@ render_frame_stroke (cairo_t        *cr,
     }
 }
 
+static void
+snapshot_frame_stroke (GtkSnapshot    *snapshot,
+                       GskRoundedRect *outline,
+                       const float     border_width[4],
+                       GdkRGBA         colors[4],
+                       guint           hidden_side,
+                       GtkBorderStyle  stroke_style)
+{
+  double double_width[4] = { border_width[0], border_width[1], border_width[2], border_width[3] };
+  cairo_t *cr;
+
+  cr = gtk_snapshot_append_cairo_node (snapshot,
+                                       &outline->bounds,
+                                       "BorderStroke");
+  render_frame_stroke (cr, outline, double_width, colors, hidden_side, stroke_style);
+  cairo_destroy (cr);
+}
+
 static void
 color_shade (const GdkRGBA *color,
              gdouble        factor,
@@ -679,6 +735,128 @@ render_border (cairo_t        *cr,
   cairo_restore (cr);
 }
 
+static void
+snapshot_border (GtkSnapshot    *snapshot,
+                 GskRoundedRect *border_box,
+                 const float     border_width[4],
+                 GdkRGBA         colors[4],
+                 GtkBorderStyle  border_style[4])
+{
+  guint hidden_side = 0;
+  guint i, j;
+
+  for (i = 0; i < 4; i++)
+    {
+      if (hidden_side & (1 << i))
+        continue;
+
+      /* NB: code below divides by this value */
+      /* a border smaller than this will not noticably modify
+       * pixels on screen, and since we don't compare with 0,
+       * we'll use this value */
+      if (border_width[i] < 1.0 / 1024)
+        continue;
+
+      switch (border_style[i])
+        {
+        case GTK_BORDER_STYLE_NONE:
+        case GTK_BORDER_STYLE_HIDDEN:
+        case GTK_BORDER_STYLE_SOLID:
+          break;
+        case GTK_BORDER_STYLE_INSET:
+          if (i == 1 || i == 2)
+            color_shade (&colors[i], 1.8, &colors[i]);
+          break;
+        case GTK_BORDER_STYLE_OUTSET:
+          if (i == 0 || i == 3)
+            color_shade (&colors[i], 1.8, &colors[i]);
+          break;
+        case GTK_BORDER_STYLE_DOTTED:
+        case GTK_BORDER_STYLE_DASHED:
+          {
+            guint dont_draw = hidden_side;
+
+            for (j = 0; j < 4; j++)
+              {
+                if (border_style[j] == border_style[i])
+                  hidden_side |= (1 << j);
+                else
+                  dont_draw |= (1 << j);
+              }
+            
+            snapshot_frame_stroke (snapshot, border_box, border_width, colors, dont_draw, border_style[i]);
+          }
+          break;
+        case GTK_BORDER_STYLE_DOUBLE:
+          {
+            GskRoundedRect other_box;
+            float other_border[4];
+            guint dont_draw = hidden_side;
+
+            for (j = 0; j < 4; j++)
+              {
+                if (border_style[j] == GTK_BORDER_STYLE_DOUBLE)
+                  hidden_side |= (1 << j);
+                else
+                  dont_draw |= (1 << j);
+                
+                other_border[j] = border_width[j] / 3;
+              }
+            
+            snapshot_frame_fill (snapshot, border_box, other_border, colors, dont_draw);
+            
+            other_box = *border_box;
+            gsk_rounded_rect_shrink (&other_box,
+                                     2 * other_border[GTK_CSS_TOP],
+                                     2 * other_border[GTK_CSS_RIGHT],
+                                     2 * other_border[GTK_CSS_BOTTOM],
+                                     2 * other_border[GTK_CSS_LEFT]);
+            snapshot_frame_fill (snapshot, &other_box, other_border, colors, dont_draw);
+          }
+          break;
+        case GTK_BORDER_STYLE_GROOVE:
+        case GTK_BORDER_STYLE_RIDGE:
+          {
+            GskRoundedRect other_box;
+            GdkRGBA other_colors[4];
+            guint dont_draw = hidden_side;
+            float other_border[4];
+
+            for (j = 0; j < 4; j++)
+              {
+                other_colors[j] = colors[j];
+                if ((j == 0 || j == 3) ^ (border_style[j] == GTK_BORDER_STYLE_RIDGE))
+                  color_shade (&other_colors[j], 1.8, &other_colors[j]);
+                else
+                  color_shade (&colors[j], 1.8, &colors[j]);
+                if (border_style[j] == GTK_BORDER_STYLE_GROOVE ||
+                    border_style[j] == GTK_BORDER_STYLE_RIDGE)
+                  hidden_side |= (1 << j);
+                else
+                  dont_draw |= (1 << j);
+                other_border[j] = border_width[j] / 2;
+              }
+            
+            snapshot_frame_fill (snapshot, border_box, other_border, colors, dont_draw);
+            
+            other_box = *border_box;
+            gsk_rounded_rect_shrink (&other_box,
+                                     other_border[GTK_CSS_TOP],
+                                     other_border[GTK_CSS_RIGHT],
+                                     other_border[GTK_CSS_BOTTOM],
+                                     other_border[GTK_CSS_LEFT]);
+            snapshot_frame_fill (snapshot, &other_box, other_border, other_colors, dont_draw);
+          }
+          break;
+        default:
+          g_assert_not_reached ();
+          break;
+        }
+    }
+  
+  snapshot_frame_fill (snapshot, border_box, border_width, colors, hidden_side);
+}
+
 gboolean
 gtk_css_style_render_has_border (GtkCssStyle *style)
 {
@@ -744,13 +922,13 @@ gtk_css_style_render_border (GtkCssStyle      *style,
 
 void
 gtk_css_style_snapshot_border (GtkCssStyle      *style,
-                               GtkSnapshot      *state,
+                               GtkSnapshot      *snapshot,
                                gdouble           width,
                                gdouble           height,
                                GtkJunctionSides  junction)
 {
   GtkBorderImage border_image;
-  double border_width[4];
+  float border_width[4];
   graphene_rect_t bounds;
   cairo_t *cr;
 
@@ -763,10 +941,12 @@ gtk_css_style_snapshot_border (GtkCssStyle      *style,
 
   if (gtk_border_image_init (&border_image, style))
     {
-      cr = gtk_snapshot_append_cairo_node (state,
+      double double_width[4] = { border_width[0], border_width[1], border_width[2], border_width[3] };
+
+      cr = gtk_snapshot_append_cairo_node (snapshot,
                                            &bounds,
                                            "Border Image");
-      gtk_border_image_render (&border_image, border_width, cr, 0, 0, width, height);
+      gtk_border_image_render (&border_image, double_width, cr, 0, 0, width, height);
       cairo_destroy (cr);
     }
   else
@@ -782,10 +962,6 @@ gtk_css_style_snapshot_border (GtkCssStyle      *style,
           border_width[3] == 0)
         return;
 
-      cr = gtk_snapshot_append_cairo_node (state,
-                                           &bounds,
-                                           "Border");
-
       border_style[0] = _gtk_css_border_style_value_get (gtk_css_style_get_value (style, GTK_CSS_PROPERTY_BORDER_TOP_STYLE));
       border_style[1] = _gtk_css_border_style_value_get (gtk_css_style_get_value (style, GTK_CSS_PROPERTY_BORDER_RIGHT_STYLE));
       border_style[2] = _gtk_css_border_style_value_get (gtk_css_style_get_value (style, GTK_CSS_PROPERTY_BORDER_BOTTOM_STYLE));
@@ -799,9 +975,7 @@ gtk_css_style_snapshot_border (GtkCssStyle      *style,
       _gtk_rounded_box_init_rect (&border_box, 0, 0, width, height);
       _gtk_rounded_box_apply_border_radius_for_style (&border_box, style, junction);
 
-      render_border (cr, &border_box, border_width, colors, border_style);
-
-      cairo_destroy (cr);
+      snapshot_border (snapshot, &border_box, border_width, colors, border_style);
     }
 }
 
@@ -904,20 +1078,19 @@ gtk_css_style_render_outline (GtkCssStyle *style,
 
 void
 gtk_css_style_snapshot_outline (GtkCssStyle *style,
-                                GtkSnapshot *state,
+                                GtkSnapshot *snapshot,
                                 gdouble      width,
                                 gdouble      height)
 {
   GtkBorderStyle border_style[4];
   GskRoundedRect border_box;
-  double border_width[4];
+  float border_width[4];
   GdkRGBA colors[4];
 
   border_style[0] = _gtk_css_border_style_value_get (gtk_css_style_get_value (style, GTK_CSS_PROPERTY_OUTLINE_STYLE));
   if (border_style[0] != GTK_BORDER_STYLE_NONE)
     {
       cairo_rectangle_t rect;
-      cairo_t *cr;
 
       compute_outline_rect (style, 0, 0, width, height, &rect);
 
@@ -930,13 +1103,7 @@ gtk_css_style_snapshot_outline (GtkCssStyle *style,
       _gtk_rounded_box_init_rect (&border_box, rect.x, rect.y, rect.width, rect.height);
       _gtk_rounded_box_apply_outline_radius_for_style (&border_box, style, GTK_JUNCTION_NONE);
 
-      cr = gtk_snapshot_append_cairo_node (state,
-                                           &GRAPHENE_RECT_INIT (rect.x, rect.y, rect.width, rect.height),
-                                           "Outline");
-
-      render_border (cr, &border_box, border_width, colors, border_style);
-
-      cairo_destroy (cr);
+      snapshot_border (snapshot, &border_box, border_width, colors, border_style);
     }
 }
 
index f89cdcb600b17bbd8de39b2475245ebcde3b6b8e..4de9d6da35595ad0ed79bfa978e76fefe287a963 100644 (file)
@@ -44,7 +44,7 @@ gboolean        gtk_css_style_render_border_get_clip    (GtkCssStyle
                                                          gdouble                 height,
                                                          GdkRectangle           *out_clip) G_GNUC_WARN_UNUSED_RESULT;
 void            gtk_css_style_snapshot_border           (GtkCssStyle            *style,
-                                                         GtkSnapshot            *state,
+                                                         GtkSnapshot            *snapshot,
                                                          gdouble                 width,
                                                          gdouble                 height,
                                                          GtkJunctionSides        junction);
@@ -57,7 +57,7 @@ void            gtk_css_style_render_outline            (GtkCssStyle
                                                          gdouble                 width,
                                                          gdouble                 height);
 void            gtk_css_style_snapshot_outline          (GtkCssStyle            *style,
-                                                         GtkSnapshot            *state,
+                                                         GtkSnapshot            *snapshot,
                                                          gdouble                 width,
                                                          gdouble                 height);
 gboolean        gtk_css_style_render_outline_get_clip   (GtkCssStyle            *style,
index fff0a06f41d1efd482bea05227162814d599a5e6..96b0d944e9b556dec50396a84bf279be84779e39 100644 (file)
@@ -527,6 +527,7 @@ append_node (GtkTreeModelRenderNode *nodemodel,
     case GSK_COLOR_NODE:
     case GSK_LINEAR_GRADIENT_NODE:
     case GSK_REPEATING_LINEAR_GRADIENT_NODE:
+    case GSK_BORDER_NODE:
       /* no children */
       break;